package com.asmtest.visitor;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * @author yutianhong
 * @version 1.0
 * @since 2023/11/28 15:32
 */
public class GenericTypeExtractor extends SignatureVisitor {
    private static final char SEPARATOR = ',';
    private static final char BEGIN = '<';
    private static final char END = '>';

    private final StringBuilder returnTypeGeneric = new StringBuilder();
    private final List<StringBuilder> paramGenerics = new ArrayList<>();
    private final Function<String, String> classNameTransfer;

    private class ParamVisitor extends SignatureVisitor {
        private final StringBuilder stringBuilder;

        public ParamVisitor(StringBuilder stringBuilder) {
            super(Opcodes.ASM9);
            this.stringBuilder = stringBuilder;
        }

        @Override
        public void visitTypeArgument() {
            // wildcard
            if (stringBuilder.length() == 0 || lastCharOf(stringBuilder) != SEPARATOR) {
                stringBuilder.append(BEGIN);
            }
            stringBuilder.append("?").append(SEPARATOR);
        }

        @Override
        public SignatureVisitor visitTypeArgument(char wildcard) {
            return new SignatureVisitor(Opcodes.ASM9) {
                private char currentWildcard = wildcard;
                private boolean array = false;

                @Override
                public SignatureVisitor visitTypeArgument(char wildcard) {
                    currentWildcard = wildcard;
                    return this;
                }

                @Override
                public SignatureVisitor visitArrayType() {
                    array = true;
                    return this;
                }

                @Override
                public void visitBaseType(char descriptor) {
                    if (stringBuilder.length() == 0 || lastCharOf(stringBuilder) != SEPARATOR) {
                        stringBuilder.append(BEGIN);
                    }
                    stringBuilder.append("[").append(descriptor).append(SEPARATOR);
                }

                @Override
                public void visitClassType(String name) {
                    if (stringBuilder.length() == 0 || lastCharOf(stringBuilder) != SEPARATOR) {
                        stringBuilder.append(BEGIN);
                    }
                    if (currentWildcard == EXTENDS) {
                        stringBuilder.append("? extends ");
                    } else if (currentWildcard == SUPER) {
                        stringBuilder.append("? super ");
                    }
                    String className = classNameTransfer.apply(name);
                    if (array) {
                        stringBuilder.append("[L");
                    }
                    stringBuilder.append(className);
                }

                @Override
                public void visitTypeArgument() {
                    if (stringBuilder.length() == 0 || lastCharOf(stringBuilder) != SEPARATOR) {
                        stringBuilder.append(BEGIN);
                    }
                    stringBuilder.append('?').append(SEPARATOR);
                }

                @Override
                public void visitTypeVariable(String name) {
                    if (stringBuilder.length() == 0 || lastCharOf(stringBuilder) != SEPARATOR) {
                        stringBuilder.append(BEGIN);
                    }
                    stringBuilder.append(name).append(SEPARATOR);
                }

                @Override
                public void visitEnd() {
                    currentWildcard = INSTANCEOF;
                    array = false;
                    if (lastCharOf(stringBuilder) == SEPARATOR) {
                        stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                        stringBuilder.append(END);
                    }
                    stringBuilder.append(SEPARATOR);
                }
            };
        }

        @Override
        public void visitEnd() {
            if (stringBuilder.length() > 0 && lastCharOf(stringBuilder) == SEPARATOR) {
                stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                if (stringBuilder.charAt(0) == BEGIN) {
                    stringBuilder.append(END);
                }
            }
        }
    }

    public GenericTypeExtractor() {
        this(e -> e);
    }

    public GenericTypeExtractor(Function<String, String> classNameTransfer) {
        super(Opcodes.ASM9);
        this.classNameTransfer = classNameTransfer;
    }

    @Override
    public SignatureVisitor visitParameterType() {
        StringBuilder stringBuilder = new StringBuilder();
        paramGenerics.add(stringBuilder);
        return new ParamVisitor(stringBuilder);
    }

    @Override
    public SignatureVisitor visitReturnType() {
        return new ParamVisitor(returnTypeGeneric);
    }

    public String getReturnTypeGeneric() {
        return returnTypeGeneric.toString();
    }

    public List<String> getParamGenerics() {
        return paramGenerics.stream().map(StringBuilder::toString).collect(Collectors.toList());
    }

    private static char lastCharOf(StringBuilder stringBuilder) {
        if (stringBuilder.length() == 0) {
            throw new IllegalArgumentException();
        }
        return stringBuilder.charAt(stringBuilder.length() - 1);
    }

    public static void main(String[] args) {
        String signature = "<T::Ljava/util/List<*>;>(Ljava/lang/String;ILjava/util/Map<Ljava/lang/Integer;Ljava/lang/Float;>;Ljava/util/Set<Ljava/util/List<Ljava/lang/Long;>;>;TT;Ljava/util/Set<Ljava/util/Map<Ljava/lang/String;Ljava/util/Map<Ljava/util/List<Ljava/lang/String;>;Ljava/lang/Long;>;>;>;Ljava/util/List<*>;Ljava/util/List<TT;>;Ljava/util/List<+Ljava/lang/Number;>;Ljava/util/List<-Ljava/util/Set<*>;>;Ljava/util/List<Ljava/util/List<TT;>;>;)Ljava/util/Map<Ljava/lang/String;TT;>;";
        System.out.println("方法签名：" + signature);
        SignatureReader signatureReader = new SignatureReader(signature);

        GenericTypeExtractor extractor = new GenericTypeExtractor(
                e -> e.lastIndexOf('/') == -1 ? e : e.substring(e.lastIndexOf("/") + 1));
        signatureReader.accept(extractor);

        System.out.println("返回类型的泛型: " + extractor.getReturnTypeGeneric());
        List<String> paramGenerics = extractor.getParamGenerics();
        for (int i = 0; i < paramGenerics.size(); i++) {
            String paramGeneric = paramGenerics.get(i);
            System.out.println("参数 " + (i + 1) + " 的泛型: " + paramGeneric);
        }
    }
}



